home *** CD-ROM | disk | FTP | other *** search
Text File | 2000-09-28 | 22.2 KB | 731 lines | [TEXT/CWIE] |
- /*
- File: OTSimpleServerHTTP.c
-
- Contains: Implementation of the simple HTTP server sample.
-
- Written by: Quinn "The Eskimo!"
-
- Copyright: Copyright © 1997-1999 by Apple Computer, Inc., All Rights Reserved.
-
- You may incorporate this Apple sample source code into your program(s) without
- restriction. This Apple sample source code has been provided "AS IS" and the
- responsibility for its operation is yours. You are not permitted to redistribute
- this Apple sample source code as "Apple sample source code" after having made
- changes. If you're going to re-distribute the source, we require that you make
- it clear in the source that the code was descended from Apple sample source
- code, but that you've made changes.
-
- Change History (most recent first):
- 7/23/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1
-
-
- */
-
- /////////////////////////////////////////////////////////////////////
- // The OT debugging macros in <OTDebug.h> require this variable to
- // be set.
- #include <Files.h>
- #include <Memory.h>
- #include <TextUtils.h>
- #ifndef qDebug
- #define qDebug 1
- #endif
-
- /////////////////////////////////////////////////////////////////////
- // Pick up all the standard OT stuff.
-
- #include <OpenTransport.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up all the OT TCP/IP stuff.
-
- #include <OpenTptInternet.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the OTDebugBreak and OTAssert macros.
-
- #include <OTDebug.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the various Thread Manager APIs.
-
- #include <Threads.h>
-
- /////////////////////////////////////////////////////////////////////
-
- #include <stdio.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up our own prototype.
-
- #include "OTSimpleServerHTTP.h"
-
- /////////////////////////////////////////////////////////////////////
- // OTDebugStr is not defined in any OT header files, but it is
- // exported by the libraries, so we define the prototype here.
-
- extern pascal void OTDebugStr(const char* str);
-
- /////////////////////////////////////////////////////////////////////
- // When this boolean is to set true, this module assumes that the
- // host application is trying to quit and all the threads created by
- // this module start dying. See the associated comment in the
- // YieldingNotifier routine.
-
- extern Boolean gQuitNow = false;
-
- // RRK Comments added 8/27/97
- // DoNegotiateIPReuseAddrOption is defined in the file
- // EnableIPReuseAddrSample.c. This call uses the OTOptionManagement function
- // to set the IP level ResuseAddr option so that an IP address can be
- // reused on immediate relaunch of the application.
- extern OSStatus DoNegotiateIPReuseAddrOption(EndpointRef ep, Boolean enableReuseIPMode);
-
- /////////////////////////////////////////////////////////////////////
-
- static pascal void YieldingNotifier(EndpointRef ep, OTEventCode code,
- OTResult result, void* cookie)
- // This simple notifier checks for kOTSyncIdleEvent and
- // when it gets one calls the Thread Manager routine
- // YieldToAnyThread. Open Transport sends kOTSyncIdleEvent
- // whenever it's waiting for something, eg data to arrive
- // inside a sync/blocking OTRcv call. In such cases, we
- // yield the processor to some other thread that might
- // be doing useful work.
- //
- // The routine also checks the gQuitNow boolean to see if the
- // the host application wants us to quit. This roundabout technique
- // avoids a number of problems including:
- //
- // 1. Threads stuck inside OT synchronous calls -- You can't just
- // call DisposeThread on a thread that's waiting for an OT
- // synchronous call to complete. Trust me, it would be bad!
- // Instead, this routine calls OTCancelSynchronousCalls to get
- // out of the call. The given error code (userCanceledErr)
- // propagates out to the caller, which causes the calling
- // thread to eventually terminate.
- // 2. Threads holding resources -- You can't just DisposeThread
- // a networking thread because it might be holding resouces,
- // like memory or endpoints, that need to be cleaned up properly.
- // Cancelling the thread in this way causes the thread's own
- // code to clean up those resources just like it would for any
- // any other error.
- //
- // I could have used a more sophisticated mechanism to support
- // quitting (such as a boolean per thread, or returning some
- // "thread object" to which the application can send a "cancel"
- // message, but this way is easy and works just fine for this
- // simple sample
- {
- #pragma unused(result)
- #pragma unused(cookie)
- OSStatus junk;
-
- switch (code) {
- case kOTSyncIdleEvent:
- junk = YieldToAnyThread();
- OTAssert("YieldingNotifier: YieldToAnyThread failed", junk == noErr);
-
- if (gQuitNow) {
- junk = OTCancelSynchronousCalls(ep, userCanceledErr);
- OTAssert("YieldingNotifier: Failed to cancel", junk == noErr);
- }
- break;
- default:
- // do nothing
- break;
- }
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static void SetDefaultEndpointModes(EndpointRef ep)
- // This routine sets the supplied endpoint into the default
- // mode used in this application. The specifics are:
- // blocking, synchronous, and using synch idle events with
- // the standard YieldingNotifier.
- {
- OSStatus junk;
-
- junk = OTSetBlocking(ep);
- OTAssert("SetDefaultEndpointModes: Could not set blocking", junk == noErr);
- junk = OTSetSynchronous(ep);
- OTAssert("SetDefaultEndpointModes: Could not set synchronous", junk == noErr);
- junk = OTInstallNotifier(ep, &YieldingNotifier, ep);
- OTAssert("SetDefaultEndpointModes: Could not install notifier", junk == noErr);
- junk = OTUseSyncIdleEvents(ep, true);
- OTAssert("SetDefaultEndpointModes: Could not use sync idle events", junk == noErr);
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static OSStatus OTSndQ(EndpointRef ep, void *buf, size_t nbytes)
- // My own personal wrapper around the OTSnd routine that cleans
- // up the error result.
- {
- OTResult bytesSent;
-
- bytesSent = OTSnd(ep, buf, nbytes, 0);
- if (bytesSent >= 0) {
-
- // Because we're running in synchronous blocking mode, OTSnd
- // should not return until it has sent all the bytes unless it
- // gets an error. If it does, we want to hear about it.
- OTAssert("OTSndQ: Not enough bytes sent", bytesSent == nbytes);
-
- return (noErr);
- } else {
- return (bytesSent);
- }
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static OSErr FSReadQ(short refNum, long count, void *buffPtr)
- // My own wrapper for FSRead. Whose bright idea was it for
- // it to return the count anyway!
- {
- OSStatus err;
- long tmpCount;
-
- tmpCount = count;
- err = FSRead(refNum, &count, buffPtr);
-
- OTAssert("FSReadQ: Did not read enough bytes", (err != noErr) || (count == tmpCount));
-
- return (err);
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static Boolean StringHasSuffix(const char *str, const char *suffix)
- // Returns true if the end of str is suffix.
- {
- Boolean result;
-
- result = false;
- if ( OTStrLength(str) >= OTStrLength(suffix) ) {
- if ( OTStrEqual(str + OTStrLength(str) - OTStrLength(suffix) , suffix) ) {
- result = true;
- }
- }
-
- return (result);
- }
-
- static OSStatus ExtractRequestedFileName(const char *buffer,
- char *fileName, char *mimeType)
- // Assuming that buffer is a C string contain an HTTP request,
- // extract the name of the file that's being requested.
- // Also check to see if the file has one of the common suffixes,
- // and set mimeType appropriately.
- //
- // Obviously this routine should use Internet Config to
- // map the file type/creator/extension to a MIME type,
- // but I don't want to complicate the sample with that code.
- {
- OSStatus err;
-
- // Default the result to empty.
- fileName[0] = 0;
-
- // Scan the request looking for the fileName. Obviously this is not
- // a very good validation of the request, but this is an OT sample,
- // not an HTTP one. Also note that we require HTTP/1.0, but some
- // ancient clients might just generate "GET %s<cr><lf>"
-
- (void) sscanf(buffer, "GET %s HTTP/1.0", fileName);
-
- // If the file name is still blank, scanf must have failed.
- // Note that I don't rely on the result from scanf because in a
- // previous life I learnt to mistrust it.
-
- if (fileName[0] == 0) {
- err = -1;
- } else {
-
- // So the request is cool. Normalise the file name.
- // Requests for the root return "index.html".
-
- if ( OTStrEqual(fileName, "/") ) {
- OTStrCopy(fileName, "index.html");
- }
-
- // Remove the prefix slash. Note that we don't deal with
- // "slashes" embedded in the fileName, so we don't handle
- // any directories other than the root. This would be
- // easy to do, but again this is not an HTTP sample.
-
- if ( fileName[0] == '/' ) {
- BlockMoveData(&fileName[1], &fileName[0], OTStrLength(fileName));
- }
-
- // Set mimeType based on the file's suffix.
-
- if ( StringHasSuffix(fileName, ".html") ) {
- OTStrCopy(mimeType, "text/html");
- } else if ( StringHasSuffix(fileName, ".gif") ) {
- OTStrCopy(mimeType, "image/gif");
- } else if ( StringHasSuffix(fileName, ".jpg") ) {
- OTStrCopy(mimeType, "image/jpeg");
- } else {
- OTStrCopy(mimeType, "text/plain");
- }
- err = noErr;
- }
-
- #if qDebug
- printf("ExtractRequestedFileName: Returning %d, “%s”, “%s”\n", err, fileName, mimeType);
- #endif
-
- return (err);
- }
-
- /////////////////////////////////////////////////////////////////////
-
- // The worker thread reads characters one at a time from the endpoint
- // and uses the following state machine to determine when the request is
- // finished. For HTTP/1.0 requests, the request is terminated by
- // two consecutive CR LF pairs. Each time we read one of the appropriate
- // characters we increment the state until we get to kDone, at which
- // point we go off to process the request.
-
- enum {
- kWorkerWaitingForCR1,
- kWorkerWaitingForLF1,
- kWorkerWaitingForCR2,
- kWorkerWaitingForLF2,
- kWorkerDone
- };
-
- // This is the size of the transfer buffer that each worker thread
- // allocates to read file system data and write network data.
-
- enum {
- kTransferBufferSize = 4096
- };
-
- // WorkerContext holds the information needed by a worker endpoint to
- // operate. A WorkerContext is created by the listener endpoint
- // and passed as the thread parameter to the worker thread. If the
- // listener successfully does this, it's assumed that the worker
- // thread has taken responsibility for freeing the context.
-
- struct WorkerContext {
- EndpointRef worker;
- short vRefNum;
- long dirID;
- };
- typedef struct WorkerContext WorkerContext, *WorkerContextPtr;
-
- // The two buffers hold standard HTTP responses. The first is the
- // default text we spit out when we get an error. The second is
- // the header that we use when we successfully field a request.
- // Again note that this sample is not about HTTP, so these responses
- // are probably not particularly compliant to the HTTP protocol.
-
- char gDefaultOutputText[] = "HTTP/1.0 200 OK\15\12Content-Type: text/html\15\12\15\12<H1>Say what!</H1><P>\15\12Error Number (%d), Error Text (%s)";
- char gHTTPHeader[] = "HTTP/1.0 200 OK\15\12Content-Type: %s\15\12\15\12";
-
- /////////////////////////////////////////////////////////////////////
-
- static OSStatus ReadHTTPRequest(EndpointRef worker, char *buffer)
- // This routine reads the HTTP request from the worker endpoint,
- // using the state machine described above, and puts it into the
- // indicated buffer. The buffer must be at least kTransferBufferSize
- // bytes big.
- //
- // This is pretty feeble
- // code (reading data one byte at a time is bad for performance),
- // but it works and I'm not quite sure how to fix it. Perhaps
- // OTCountDataBytes?
- //
- // Also, the code does not support with requests bigger than
- // kTransferBufferSize. In practise, this isn't a problem.
- {
- OSStatus err;
- long bufferIndex;
- int state;
- char ch;
- OTResult bytesReceived;
- OTFlags junkFlags;
-
- OTAssert("ReadHTTPRequest: What endpoint?", worker != nil);
- OTAssert("ReadHTTPRequest: What buffer?", buffer != nil);
-
- bufferIndex = 0;
- state = kWorkerWaitingForCR1;
- do {
- bytesReceived = OTRcv(worker, &ch, sizeof(char), &junkFlags);
- if (bytesReceived >= 0) {
- OTAssert("ReadHTTPRequest: Didn't read the expected number of bytes", bytesReceived == sizeof(char));
-
- err = noErr;
-
- // Put the character into the buffer.
-
- buffer[bufferIndex] = ch;
- bufferIndex += 1;
-
- // Check that we still have space to include our null terminator.
-
- if (bufferIndex >= kTransferBufferSize) {
- err = -1;
- }
-
- // Do the magic state machine. Note the use of
- // hardwired numbers for CR and LF. This is correct
- // because the Internet standards say that these
- // numbers can't change. I don't use \n and \r
- // because these values change between various C
- // compilers on the Mac.
-
- switch (ch) {
- case 13:
- switch (state) {
- case kWorkerWaitingForCR1:
- state = kWorkerWaitingForLF1;
- break;
- case kWorkerWaitingForCR2:
- state = kWorkerWaitingForLF2;
- break;
- default:
- state = kWorkerWaitingForCR1;
- break;
- }
- break;
- case 10:
- switch (state) {
- case kWorkerWaitingForLF1:
- state = kWorkerWaitingForCR2;
- break;
- case kWorkerWaitingForLF2:
- state = kWorkerDone;
- break;
- default:
- state = kWorkerWaitingForCR1;
- break;
- }
- break;
- default:
- state = kWorkerWaitingForCR1;
- break;
- }
- } else {
- err = bytesReceived;
- }
- } while ( err == noErr && state != kWorkerDone );
-
- if (err == noErr) {
- // Append the null terminator that turns the HTTP request into a C string.
- buffer[bufferIndex] = 0;
- }
-
- return (err);
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static OSStatus CopyFileToEndpoint(const FSSpec *fileSpec, char *buffer, EndpointRef worker)
- // Copy the file denoted by fileSpec to the endpoint. buffer is a
- // temporary buffer of size kTransferBufferSize. Initially buffer
- // contains a C string that is the HTTP header to output. After that,
- // the routine uses buffer as temporary storage. We do this because
- // we want any errors opening the file to be noticed before we send
- // the header saying that the request went through successfully.
- {
- OSStatus err;
- OSStatus junk;
- long bytesToSend;
- long bytesThisTime;
- short fileRefNum;
-
- err = FSpOpenDF(fileSpec, fsRdPerm, &fileRefNum);
- if (err == noErr) {
- err = GetEOF(fileRefNum, &bytesToSend);
-
- // Write the HTTP header out to the endpoint.
-
- if (err == noErr) {
- err = OTSndQ(worker, buffer, OTStrLength(buffer));
- }
-
- // Copy the file in kTransferBufferSize chunks to the endpoint.
-
- while (err == noErr && bytesToSend > 0) {
- if (bytesToSend > kTransferBufferSize) {
- bytesThisTime = kTransferBufferSize;
- } else {
- bytesThisTime = bytesToSend;
- }
- err = FSReadQ(fileRefNum, bytesThisTime, buffer);
- if (err == noErr) {
- err = OTSndQ(worker, buffer, bytesThisTime);
- }
- bytesToSend -= bytesThisTime;
- }
-
- // Clean up.
- junk = FSClose(fileRefNum);
- OTAssert("WorkerThreadProc: Could not close file", junk == noErr);
- }
-
- return (err);
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static pascal OSStatus WorkerThreadProc(WorkerContextPtr context)
- // This routine is the starting routine for the worker thread.
- // The thread is responsible for reading an HTTP request from
- // an endpoint, processing the requesting and writing the results
- // back to the endpoint.
- {
- OSStatus err;
- OSStatus junk;
- char *buffer = nil;
- char *errStr;
- char fileName[256];
- char mimeType[256];
- FSSpec fileSpec;
-
- printf("WorkerThreadProc: Starting\n");
- fflush(stdout);
- OTAssert("WorkerThreadProc: Context is nil!", context != nil);
- OTAssert("WorkerThreadProc: Worker endpoint is nil!", context->worker != nil);
-
- // Allocate the transfer buffer in the heap.
-
- err = noErr;
- buffer = OTAllocMem(kTransferBufferSize);
- if (buffer == nil) {
- err = kENOMEMErr;
- }
-
- // Read the request into the transfer buffer.
-
- if (err == noErr) {
- err = ReadHTTPRequest(context->worker, buffer);
- }
-
- if (err == noErr) {
-
- // Get the requested file name (and it's mimeType) from the
- // HTTP request in the transfer buffer.
-
- err = ExtractRequestedFileName(buffer, fileName, mimeType);
-
- if (err == noErr) {
-
- // Create the appropriate HTTP response in the buffer.
-
- sprintf(buffer, gHTTPHeader, mimeType);
-
- // Copy the file (with preceding HTTP header) to the endpoint.
-
- (void) FSMakeFSSpec(context->vRefNum, context->dirID, C2PStr(fileName), &fileSpec);
- err = CopyFileToEndpoint(&fileSpec, buffer, context->worker);
- }
-
- // Handle any errors by sending back an appropriate error header.
-
- if (err != noErr) {
- switch (err) {
- case fnfErr:
- errStr = "File Not Found";
- break;
- default:
- errStr = "Unknown Error";
- break;
- }
- sprintf(buffer, gDefaultOutputText, err, errStr);
- err = OTSndQ(context->worker, buffer, OTStrLength(buffer));
- }
- }
-
- // Shut down the endpoint and clean up the WorkerContext.
-
- if (err == noErr) {
- err = OTSndOrderlyDisconnect(context->worker);
- if (err == noErr) {
- err = OTRcvOrderlyDisconnect(context->worker);
- }
- }
-
- junk = OTCloseProvider(context->worker);
- OTAssert("StartHTTPServer: Could not close listener", junk == noErr);
-
- OTFreeMem(context);
-
- if (buffer != nil) {
- OTFreeMem(buffer);
- }
-
- printf("WorkerThreadProc: Stopping with final result %d.\n", err);
- fflush(stdout);
-
- return (noErr);
- }
-
- /////////////////////////////////////////////////////////////////////
-
- OSStatus RunHTTPServer(InetHost ipAddr, short vRefNum, long dirID)
- // This routine runs an HTTP server. It doesn't return until
- // someone sets gQuitNow, so you should most probably call this
- // routine on its own thread. ipAddr is the IP address that
- // the server listens on. Specify kOTAnyInetAddress to listen
- // on all IP addresses on the machine; specify an IP address
- // to listen on a specific address. vRefNum and dirID point
- // to the root directory of the HTTP information to be served.
- //
- // The routine creates a listening endpoint and listens for connection
- // requests on that endpoint. When a connection request arrives, it creates
- // a new worker thread (with accompanying endpoint) and accepts the connection
- // on that thread.
- //
- // Note the use of the "tilisten" module which prevents multiple
- // simultaneous T_LISTEN events coming from the transport provider,
- // thereby greatly simplifying the listen/accept sequence.
- {
- OSStatus err;
- EndpointRef listener;
- TBind bindReq;
- InetAddress ipAddress;
- InetAddress remoteIPAddress;
- TCall call;
- ThreadID workerThread;
- OSStatus junk;
- WorkerContextPtr workerContext;
- TEndpointInfo Info;
- char buf[128];
-
- // display IP address in String
- OTInetHostToString(ipAddr, buf);
- printf("HTTP Server on <%s> Starting.\n", buf);
-
- fflush(stdout);
-
- // Create the listen endpoint.
-
- // RRK comments added 8/27/97
- // In order for the IP address to be re-used after quitting this sample program and
- // restarting it, the "ReuseAddr" option must be set.
- // One should be able to set the "ReuseAddr" option as part of the configuration string
- // however, if you try to do this along with specifying the use of the tilisten
- // module, the following code hangs the system hard. As an alternative, the
- // endpoint is created with the tilisten module layered above tcp. After that the
- // OTOptionManagement call is made to set this option.
- // RRK Comments end
-
- //listener = OTOpenEndpoint(OTCreateConfiguration("tilisten,tcp(ReuseAddr=1)"), 0, nil, &err);
- listener = OTOpenEndpoint(OTCreateConfiguration("tilisten,tcp"), 0, &Info, &err);
-
- // RRK addition 8/27/97
- // set the ReuseAddr option
- if (err == noErr) {
- junk = DoNegotiateIPReuseAddrOption(listener, true);
- OTAssert("Unable to negotiate raw mode for listener endpoint", junk == noErr);
- }
- // end RRK addition 8/27/97
-
- // Set the endpoint mode and bind it to the appropriate IP address.
-
- if (err == noErr) {
- SetDefaultEndpointModes(listener);
- OTInitInetAddress(&ipAddress, 80, ipAddr); // port & host ip
- bindReq.addr.buf = (UInt8 *) &ipAddress;
- bindReq.addr.len = sizeof(ipAddress);
- bindReq.qlen = 1;
- err = OTBind(listener, &bindReq, nil);
- }
-
- while (err == noErr) {
-
- // Listen for connection attempts...
-
- OTMemzero(&call, sizeof(TCall));
- call.addr.buf = (UInt8 *) &remoteIPAddress;
- call.addr.maxlen = sizeof(remoteIPAddress);
- err = OTListen(listener, &call);
-
- // ... then spool a worker thread for this connection.
-
- if (err == noErr) {
-
- // Create the worker context.
-
- workerThread = kNoThreadID;
- workerContext = OTAllocMem(sizeof(WorkerContext));
- if (workerContext == nil) {
- err = kENOMEMErr;
- } else {
- workerContext->worker = nil;
- workerContext->vRefNum = vRefNum;
- workerContext->dirID = dirID;
- }
-
- // Open the worker endpoint.
-
- if (err == noErr) {
- workerContext->worker = OTOpenEndpoint(OTCreateConfiguration("tcp"), 0, nil, &err);
- if (err == noErr) {
- SetDefaultEndpointModes(workerContext->worker);
- }
- }
-
- // Create the worker thread.
-
- if (err == noErr) {
- err = NewThread(kCooperativeThread,
- (ThreadEntryProcPtr) WorkerThreadProc, workerContext,
- 0, kNewSuspend | kCreateIfNeeded,
- nil,
- &workerThread);
- }
-
- // Accept the connection on the thread.
-
- if (err == noErr) {
- err = OTAccept(listener, workerContext->worker, &call);
- }
-
- // Schedule the thread for execution.
-
- if (err == noErr) {
- err = SetThreadState(workerThread, kReadyThreadState, kNoThreadID);
- }
-
- // Clean up on error.
-
- if (err != noErr) {
- if (workerContext != nil) {
- if (workerContext->worker != nil) {
- junk = OTCloseProvider(workerContext->worker);
- OTAssert("StartHTTPServer: Could not close worker", junk == noErr);
- }
- OTFreeMem(workerContext);
- }
- if (workerThread != kNoThreadID) {
- junk = DisposeThread(workerThread, nil, true);
- OTAssert("StartHTTPServer: DisposeThread failed", junk == noErr);
- }
- printf("StartHTTPServer: Failed to spool worker, error %d.\n", err);
- fflush(stdout);
- err = noErr;
- }
- }
- }
-
- // Clean up the listener endpoint.
-
- if (listener != nil) {
- junk = OTCloseProvider(listener);
- OTAssert("StartHTTPServer: Could not close listener", junk == noErr);
- }
-
- printf("HTTP Server on %08x: Stopping.\n", ipAddr);
- fflush(stdout);
-
- return (err);
- }
-